﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using BEPUphysics.Entities;
using Asplode.GameEntity.Characters;
using Microsoft.Xna.Framework.Input;
using BEPUphysics;
using FlyingGamePC.GameEntity.Weapons;
using Asplode.GameEntity;
using Asplode.InputHandler;
using BEPUphysics.Events;

namespace Asplode.GameWorld
{
    public class GameWorld
    {
        #region Properties

        private List<Human> _humans = new List<Human>();
        public List<Human> Humans
        {
            get
            {
                return _humans;
            }
        }

        private List<Zombie> _zombies = new List<Zombie>();
        public List<Zombie> Zombies
        {
            get
            {
                return _zombies;
            }
        }

        private List<Entity> _entList = new List<Entity>();
        public List<Entity> EntList
        {
            get
            {
                return _entList;
            }
        }

        private Human _player;
        public Human Player
        {
            get
            {
                return _player;
            }
        }
        
        private AsplodeMap _map;
        public AsplodeMap Map
        {
            get
            {
                return _map;
            }
        }

        private InputManager _inputManager;
        public InputManager InputManager
        {
            get
            {
                return _inputManager;
            }
        }

        private EventDispatcher _eventDispatcher;
        public EventDispatcher EventDispatcher
        {
            get
            {
                return _eventDispatcher;
            }
        }

        private EntityFactory _entityFactory;
        public EntityFactory GWEntityFactory
        {
            get
            {
                return _entityFactory;
            }
        }

        public bool MoveZombies { get; set; }

        #endregion

        private List<Entity> entitiesToRemove = new List<Entity>();

        public GameWorld(EventDispatcher handler, AsplodeMap map, InputManager manager)
        {
            _eventDispatcher = handler;

            _map = map;
            _entityFactory = new EntityFactory(this);
            _inputManager = manager;

            InitializeInputKeys();
        }

        private void InitializeInputKeys()
        {
            InputManager["FirstPerson"].KeyboardKey = Keys.F2;
            InputManager["FirstPerson"].UseKeyboardKey = true;
            InputManager["FirstPerson"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleFirstPerson);

            InputManager["ThirdPerson"].KeyboardKey = Keys.F3;
            InputManager["ThirdPerson"].UseKeyboardKey = true;
            InputManager["ThirdPerson"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleThirdPerson);

            //can also initialize like this
            InputManager["TopDown"].KeyboardKey = Keys.F1;
            InputManager["TopDown"].UseKeyboardKey = true;
            InputManager["TopDown"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleTopDown);

            InputManager["Exit"].KeyboardKey = Keys.Escape;
            InputManager["Exit"].UseKeyboardKey = true;
            InputManager["Exit"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleGameExit);

            //Could simulate full auto/semi auto switching by having an even add/remove this event handler from the input manager
            //and add it to ActionDown instead
            InputManager["Shoot"].MouseButton = MouseButtons.Left;
            InputManager["Shoot"].UseMouseButton = true;
            InputManager["Shoot"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleShoot);

            InputManager["BoxGun"].KeyboardKey = Keys.D1;
            InputManager["BoxGun"].UseKeyboardKey = true;
            InputManager["BoxGun"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleWeaponSwitch);

            InputManager["Mortar"].KeyboardKey = Keys.D2;
            InputManager["Mortar"].UseKeyboardKey = true;
            InputManager["Mortar"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleWeaponSwitch);

            InputManager["Forward"].KeyboardKey = Keys.W;
            InputManager["Forward"].UseKeyboardKey = true;
            //InputManager["Forward"].ActionUp += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonUp);
            //InputManager["Forward"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonDown);
            InputManager["Forward"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonHeld);

            InputManager["Backward"].KeyboardKey = Keys.S;
            InputManager["Backward"].UseKeyboardKey = true;            
            //InputManager["Backward"].ActionUp += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonUp);
            //InputManager["Backward"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonDown);
            InputManager["Backward"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonHeld);

            InputManager["RotateCCW"].KeyboardKey = Keys.A;
            InputManager["RotateCCW"].UseKeyboardKey = true;
            //InputManager["RotateCCW"].ActionUp += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonUp);
            //InputManager["RotateCCW"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonDown);
            InputManager["RotateCCW"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonHeld);


            InputManager["RotateCW"].KeyboardKey = Keys.D;
            InputManager["RotateCW"].UseKeyboardKey = true;
            //InputManager["RotateCW"].ActionUp += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonUp);
            //InputManager["RotateCW"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonDown);
            InputManager["RotateCW"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleMoveButtonHeld);


            //InputManager["RotateWeaponCCW"].KeyboardKey = Keys.Q;
            //InputManager["RotateWeaponCCW"].UseKeyboardKey = true;
            //InputManager["RotateWeaponCCW"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleWeaponRotate);

            //InputManager["RotateWeaponCW"].KeyboardKey = Keys.E;
            //InputManager["RotateWeaponCW"].UseKeyboardKey = true;
            //InputManager["RotateWeaponCW"].ActionHeld += new InputActionDelegate(GameWorldEventHandler.HandleWeaponRotate);

            //InputManager["CenterWeapon"].KeyboardKey = Keys.C;
            //InputManager["CenterWeapon"].UseKeyboardKey = true;
            //InputManager["CenterWeapon"].ActionDown += new InputActionDelegate(GameWorldEventHandler.HandleCenterWeapon);

            InputManager["MouseAim"].UseMouseMove = true;
            //InputManager["MouseAim"].UpdateMouseAlways = true;
            InputManager["MouseAim"].MouseMove += new InputActionAxisDelegate(GameWorldEventHandler.HandleAim);

            InputManager["MoveZombies"].KeyboardKey = Keys.M;
            InputManager["MoveZombies"].UseKeyboardKey = true;
            InputManager["MoveZombies"].ActionDown += new InputActionDelegate(GameWorldEventHandler.MoveZombies);
        }

        public void Update(GameTime gameTime)
        {
            InputManager.Update(gameTime);

            UpdateLocation();

            UpdateZombies(gameTime);

            foreach (Entity entity in entitiesToRemove)
            {
                EventDispatcher.RaiseRemoveEntity(entity);
            }
            entitiesToRemove.Clear();
        }

        private void UpdateZombies(GameTime gameTime)
        {
            if (MoveZombies)
            {
                foreach (Zombie Zombie in Zombies)
                {
                    // We have to use internalCenterOfMass here, since it's possible
                    // that we just created one of these characters and the physics
                    // engine hasn't had time to move them to the location we
                    // requested.
                    Vector3 zombToPlayer =
                        (Player.gameObject.body.internalCenterOfMass
                         - Zombie.gameObject.body.internalCenterOfMass);
                    if (zombToPlayer.LengthSquared() < 1000)
                    {
                        Zombie.rotation = (float)(Math.Atan2(
                            -zombToPlayer.Z,
                            zombToPlayer.X));

                        Zombie.gameObject.body.orientationMatrix =
                            Matrix.CreateRotationY(Zombie.rotation);

                        // If we're out of range, try to move closer.
                        if (zombToPlayer.Length() > Zombie.weapon.maxRange)
                        {
                            float acceleration = (float)(10 * gameTime.ElapsedGameTime.TotalSeconds);

                            Vector3 impulse = zombToPlayer;
                            impulse.Normalize();
                            impulse *= acceleration * Zombie.gameObject.body.mass;
                            Zombie.gameObject.body.applyLinearImpulse(impulse);
                        }
                        else if (Zombie.weapon.CanShoot())
                        {
                            Zombie.weapon.Shoot(Zombie, Player, GWEntityFactory);
                        }
                    }
                }
            }
        }

        private void UpdateLocation()
        {
            if (Player.gameObject.body.linearVelocity.LengthSquared() > 100.0f)
            {
                Vector3 newVelocity = Player.gameObject.body.linearVelocity;
                newVelocity.Normalize();
                newVelocity *= 10.0f;
                Player.gameObject.body.linearVelocity = newVelocity;
                //Player.gameObject.body.linearVelocity.Normalize();
                //Player.gameObject.body.linearVelocity *= 10.0f;
            }
            if (Map.Camera.CameraMode == AsplodeCamera.Mode.FirstPerson)
            {
                Map.Camera.UpdateFirstPersonMode(Player);
            }
            else if (Map.Camera.CameraMode == AsplodeCamera.Mode.ThirdPerson)
            {
                Map.Camera.UpdateThirdPersonMode(Player);
            } 
        }

        public void ClearWorld()
        {
            while (_entList.Count > 0)
            {
                EventDispatcher.RaiseRemoveEntity(_entList[_entList.Count - 1]);
            }
            _player = null;
        }

        public void Initialize()
        {
            Entity Floor = GWEntityFactory.createFloor();
            EventDispatcher.RaiseBoxSpawn(Floor as Box);

            Entity Zombie = GWEntityFactory.createZombie(new Vector3(5.0f, 1.5f, 5.0f));
            EventDispatcher.RaiseZombieSpawn(Zombie);

            Zombie = GWEntityFactory.createZombie(new Vector3(-5.0f, 1.5f, -5.0f));
            EventDispatcher.RaiseZombieSpawn(Zombie);

            Entity PlayerEntity = GWEntityFactory.createPlayer(new Vector3(0, 1.5f, 0));
            EventDispatcher.RaiseHumanSpawn(PlayerEntity);

            Entity cursorEntity = GWEntityFactory.createCursor();
            EventDispatcher.RaiseCursorSpawn(PlayerEntity, cursorEntity);

            _player = new Human(new GameObject(PlayerEntity), cursorEntity);
        }

        public void boxShotHit(Entity sender, Entity other, CollisionPair collisionPair)
        {
            //HACK ALERT we need a projectile class to make this cleaner
            if (sender.tag.Equals(other.tag))
            {
                return;
            }

            EventDispatcher.Instance.RaiseRemoveEntity(sender);

            Box senderBox = sender as Box;
            Box box = GWEntityFactory.createBox(
                senderBox.centerOfMass, 
                senderBox.width * 5f, 
                senderBox.height * 5f, 
                senderBox.length * 5f, 
                senderBox.mass);
            EventDispatcher.RaiseBoxSpawn(box);

            (senderBox.tag as BoxGun).addShotBox(box);
        }

        public void mortarShotHit(Entity sender, Entity other, CollisionPair collisionPair)
        {
            ///HACK ALERT not all spheres are mortar shots.
            if ((other as Sphere) != null) return;

            //if (other == acceptedTrigger)
            Zombie zombie = other.tag as Zombie;
            if (zombie != null)
            {
                // Don't remove the zombie right now -- instead, watch them be pushed around by the explosion.
                //entitiesToRemove.Add(other);
            }

            EventDispatcher.RaiseExplosion(sender.centerOfMass, 10, 10);

            // Delete the shell.
            entitiesToRemove.Add(sender);


            //physics.remove(other);
            /*
            {//If the detector collided with the accepted trigger, move the box.
                spawnedBox.centerPosition = new Vector3(4, 5, 0);
                spawnedBox.orientationQuaternion = Quaternion.Identity;
                spawnedBox.linearVelocity = Vector3.Zero;
                spawnedBox.angularVelocity = Vector3.Zero;
            }
            */
        }

        public class EntityFactory
        {
            private GameWorld gameWorld;

            public EntityFactory(GameWorld gameWorld)
            {
                this.gameWorld = gameWorld;
            }

            public Entity createZombie(Vector3 pos)
            {
                Cylinder cyl = new Cylinder(pos + (2f * Vector3.Up), 8, 1, 5);
                cyl.bounciness = 0;
                cyl.angularDamping = 1;
                cyl.tag = new Zombie(new GameObject(cyl));
                return cyl;

            }


            public Box createBox(Vector3 pos, float width, float height, float length, float mass)
            {
                Box box = new Box(pos, width, height, length, mass);
                return box;
            }

            public Entity createFloor()
            {
                Box box = new Box(Vector3.Zero, 800, 1, 600);
                return box;
            }

            public Entity createPlayer(Vector3 pos)
            {
                Cylinder cyl = new Cylinder(pos + (2f * Vector3.Up), 2, 1, 5);
                cyl.linearDamping = .8f;
                cyl.angularDamping = 1;
                return cyl;
            }

            public Entity createCursor()
            {
                return new Sphere(Vector3.Zero+0.5f*Vector3.Up,0);
            }

            public Entity createMortarShell(Vector3 pos)
            {
                // A mortar shell should be pretty heavy so it isn't deflected much by explosions.
                Sphere mortarShell = new Sphere(pos, 0.3f, 1000f);

                mortarShell.eventManager.addEventHook(new EventHandlerInitialCollisionDetected(gameWorld.mortarShotHit));
                return mortarShell;
            }

            public Entity createExplosion(Vector3 pos)
            {
                Sphere explosion = new Sphere(pos, 0.5f); // infinite mass
                //explosion.becomeKinematic();
                return explosion;
            }
        }
    }
}
